package org.openstreetmap.josm.plugins.contourmerge; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Cursor; import java.awt.Point; import java.awt.dnd.DragSource; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.logging.Logger; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.mapmode.MapMode; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.BBox; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.data.osm.WaySegment; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.help.HelpUtil; import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; /** * <p>ContourMergeMode is the {@link MapMode} for merging the contours of * two areas.</p> * */ @SuppressWarnings("serial") public class ContourMergeMode extends MapMode { @SuppressWarnings("unused") static private final Logger logger = Logger.getLogger(ContourMergeMode.class.getName()); private Collection<OsmPrimitive> selection; public ContourMergeMode(MapFrame mapFrame) { super( tr("Contour Merge"), // name "contourmerge", // icon name tr("Merge the contour of an area with the contour of " + "another area"), // tooltip Shortcut.registerShortcut("contourmerge:activate", tr("Contour Merge: Activate Mode"), KeyEvent.VK_B, Shortcut.NONE // don't assign an action group, let the // user assign it in the preferences ), mapFrame, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR) ); putValue("help", HelpUtil.ht("Plugin/ContourMerge")); } protected MapView getMapView(){ return Main.map.mapView; } protected Optional<ContourMergeModel> getActiveModel() { return ContourMergePlugin.getModelManager().getActiveModel(); } @Override public void enterMode() { super.enterMode(); getMapView().addMouseListener(this); getMapView().addMouseMotionListener(this); ContourMergePlugin.setEnabled(true); getActiveModel().ifPresent(model -> {; model.reset(); /* * Remind the current selection and clear it; otherwise the * rendered selection might interfere with our understanding of * "selected" nodes and way slices in this map mode. */ selection = new ArrayList<>( model.getLayer().data.getSelected() ); model.getLayer().data.clearSelection(); }); } @Override public void exitMode() { super.exitMode(); getMapView().removeMouseListener(this); getMapView().removeMouseMotionListener(this); ContourMergePlugin.setEnabled(false); getActiveModel().ifPresent(model -> { model.reset(); /* * Restore the last selection, but remove primitives from the * selection which are not in the dataset anymore. */ DataSet ds = model.getLayer().data; selection.removeIf(p -> ds.getPrimitiveById(p) == null); model.getLayer().data.setSelected(selection); selection = null; }); } @Override public boolean layerIsSupported(Layer l) { return l instanceof OsmDataLayer; } @Override public void mouseReleased(MouseEvent e) { if (! ContourMergePlugin.isEnabled()) return; onDrop(e.getPoint()); } @Override public void mousePressed(MouseEvent e) { if (! ContourMergePlugin.isEnabled()) return; getActiveModel().ifPresent(model -> onStartDrag(e.getPoint())); } @Override public void mouseClicked(MouseEvent e) { if (! ContourMergePlugin.isEnabled()) return; if (e.getButton() != MouseEvent.BUTTON1) return; getActiveModel().ifPresent(model -> { List<Node> candidates = getMapView().getNearestNodes(e.getPoint(), OsmPrimitive::isSelectable); if (!candidates.isEmpty()){ if (!OsmPrimitive.getFilteredList( candidates.get(0).getReferrers(), Way.class).isEmpty()) { /* * clicked on a node which isn't isolated ? => toggle its * selected state */ model.toggleSelected(candidates.get(0)); } } getMapView().repaint(); }); } protected BBox buildSnapBBox(Point p){ MapView mv = Main.map.mapView; LatLon ll = mv.getLatLon(p.x -3, p.y - 3); LatLon ur = mv.getLatLon(p.x + 3, p.y + 3); return new BBox(ll, ur); } protected void showHelpText(String text){ Main.map.statusLine.setHelpText(text); } @Override public void mouseMoved(MouseEvent e) { if (! ContourMergePlugin.isEnabled()) return; getActiveModel().ifPresent(model -> { if (e.getButton() != MouseEvent.NOBUTTON) return; List<Node> candidates = getMapView().getNearestNodes(e.getPoint(), OsmPrimitive::isSelectable); showHelpText(""); if (candidates.isEmpty()){ model.setFeedbackNode(null); WaySegment ws = getMapView().getNearestWaySegment(e.getPoint(), OsmPrimitive::isSelectable); if (ws == null){ getMapView().setCursor(Cursor.getDefaultCursor()); model.setDragStartFeedbackWaySegment(null); } else { showHelpText(tr("Drag/drop: drag the way segment an drop " + "it on a target segment")); getMapView().setCursor(Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR)); model.setDragStartFeedbackWaySegment(ws); } } else { if (model.isSelected(candidates.get(0))) { showHelpText(tr("Left-Click: deselect node")); getMapView().setCursor(ImageProvider.getCursor("normal", "deselect_node")); } else { if (OsmPrimitive.getFilteredList( candidates.get(0).getReferrers(), Way.class).isEmpty()) { showHelpText(tr("Can''t select an isolated node")); getMapView().setCursor(DragSource.DefaultMoveNoDrop); } else { showHelpText(tr("Left-Click: select node")); getMapView().setCursor(ImageProvider.getCursor("normal", "select_node")); } } model.setFeedbackNode(candidates.get(0)); } Main.map.mapView.repaint(); }); } @Override public void mouseEntered(MouseEvent e) {/* ignore */} @Override public void mouseExited(MouseEvent e) {/* ignore */} @Override public void mouseDragged(MouseEvent e) { onStepDrag(e.getPoint()); } /* ----------------------------------------------------------------------*/ /* drag and drop */ /* --------------------------------------------------------------------- */ protected Point dragStart = null; protected void onStartDrag(Point start) { getActiveModel().ifPresent(model -> { WaySegment ws = getMapView().getNearestWaySegment(start, OsmPrimitive::isSelectable); if (ws != null && model.isWaySegmentDragable(ws)) { this.dragStart = start; getMapView().setCursor(Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR)); showHelpText(tr( "Drag the way segment and drop it on a target segment")); model.setDragOffset(new Point(0,0)); model.setDragStartFeedbackWaySegment(ws); model.setDropFeedbackSegment(null); } }); } protected void onStepDrag(Point current){ if (dragStart == null) return; // drag initiated outside of map view ? final WaySegment ws = getMapView().getNearestWaySegment(current, OsmPrimitive::isSelectable); final boolean isPotentialDropTarget = getActiveModel() .filter(model -> model.isPotentialDropTarget(ws)) .isPresent(); WaySegment newDropTargetFeedbackSegment; if (ws == null){ /* * mouse pointer isn't close to another way, continue dragging */ getMapView().setCursor(Cursor.getPredefinedCursor( Cursor.MOVE_CURSOR)); showHelpText(tr( "Drag the way segment and drop it on a target segment")); newDropTargetFeedbackSegment = null; } else if (!isPotentialDropTarget) { /* * mouse pointer is close to a way segment which isn't part of * a potential target way slice */ getMapView().setCursor(DragSource.DefaultLinkNoDrop); showHelpText(tr( "Drag the way segment and drop it on a target segment" )); newDropTargetFeedbackSegment = null; } else { /* * mouse pointer is close to a way segment which is part of a * potential target way slice */ getMapView().setCursor(DragSource.DefaultLinkDrop); showHelpText(tr("Drop to align to the target segment")); newDropTargetFeedbackSegment = ws; } final Point offset = new Point( current.x - dragStart.x, current.y - dragStart.y ); getActiveModel().ifPresent(model -> { model.setDragOffset(offset); model.setDropFeedbackSegment(newDropTargetFeedbackSegment); }); Main.map.mapView.repaint(); } protected void onDrop(Point target){ if (dragStart == null) return; // drag initiated outside of map view ? final WaySegment ws = getMapView().getNearestWaySegment(target, OsmPrimitive::isSelectable); getActiveModel().ifPresent(model -> { if (model.isPotentialDropTarget(ws)){ /* * Merge the way slice given by the drag source onto the way * slice given by the drop target. */ getMapView().setCursor(Cursor.getDefaultCursor()); Command cmd = model.buildContourAlignCommand(); if (cmd != null){ Main.main.undoRedo.add(cmd); } } /* * Reset the drag state */ getMapView().setCursor(Cursor.getDefaultCursor()); showHelpText(tr("Left-Click: on node to select/unselect; " + "Drag: drag way slice")); this.dragStart = null; model.setDragStartFeedbackWaySegment(null); model.setDropFeedbackSegment(null); model.setDragOffset(null); Main.map.mapView.repaint(); }); } }